#include <assert.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <fcntl.h>
#include <unistd.h>

#include "../au.h"
#include "../common.h"
#include "afile.h"

enum {
	A_NULL,
	A_SIZE,
	A_ENC,
	A_RATE,
	A_CHAN
};

enum {
	AF_HEADER,
	AF_DATA,
	AF_DONE,
	AF_EOF
};

struct afile {
	struct au	 au;
	uint8_t		 buffer[AU_SIZE];
	size_t		 index;
	off_t		 data;
	int		 state;
	int		 fd;
};

const struct a_lookup stab[] = {
	{ "size", A_SIZE },
	{ "enc", A_ENC },
	{ "encoding", A_ENC },
	{ "rate", A_RATE },
	{ "chan", A_CHAN },
	{ "channel", A_CHAN },
	{ "channels", A_CHAN },
	{ NULL, A_NULL }
};

struct afile *
af_open(const char *file)
{
	struct afile *af;
	int fd;

	if (file == NULL)
		ERR(EINVAL, NULL);
	if ((fd = open(file, O_RDONLY)) < 0)
		return (NULL);
	if ((af = af_fdopen(fd)) == NULL)
		(void)close(fd);
	return (af);
}

struct afile *
af_fdopen(int fd)
{
	struct afile *a;

	if (fd < 0)
		ERR(EINVAL, NULL);
	if ((a = malloc(sizeof(struct afile))) == NULL)
		return (NULL);
	a->index = 0;
	a->data = -1;
	a->state = AF_HEADER;
	a->fd = fd;
	return (a);
}

void
af_close(struct afile *a)
{

	if (a == NULL)
		return;
	(void)close(a->fd);
	free(a);
}

int
af_get(struct afile *a, const char *s, ...)
{
	va_list ap;
	ssize_t r;
	int n;
	int v;

	if (a == NULL || s == NULL)
		ERR(EINVAL, -1);
	if (a->state != AF_DATA)
		ERR(EINVAL, -1);
	va_start(ap, s);
	for (n = 0; (r = a_lookup(stab, s, &v)) > 0; n++, s += r)
		switch (v) {
		case A_SIZE:
			*va_arg(ap, uint32_t *) = a->au.au_size;
			break;
		case A_ENC:
			*va_arg(ap, uint32_t *) = a->au.au_enc;
			break;
		case A_RATE:
			*va_arg(ap, uint32_t *) = a->au.au_rate;
			break;
		case A_CHAN:
			*va_arg(ap, uint32_t *) = a->au.au_chan;
			break;
		default:
			goto err;
		}
	if (r == 0) {
		va_end(ap);
		return (n);
	}
err:
	va_end(ap);
	ERR(EINVAL, n);
}

int
af_set(struct afile *a, const char *s, ...)
{

	ERR(EOPNOTSUPP, -1);
}

ssize_t
af_read(struct afile *a, void *b, size_t s)
{
	ssize_t r;

	if (a == NULL && b == NULL)
		ERR(EFAULT, -1);
	if (a->state == AF_DATA)
		s -= s % au_ssize(&a->au);
	for (;;)
		switch (a->state) {
		case AF_HEADER:
			if ((r = nread(a->fd, a->buffer + a->index,
			    AU_SIZE - a->index)) <= 0)
				return (r);
			if ((a->index += r) >= AU_SIZE) {
				a->index = 0;
				a->data = lseek(a->fd, 0, SEEK_CUR);
				if (au_gethdr(&a->au, a->buffer) != 0)
					ERR(EINVAL, -1);
				a->state = a->au.au_size == 0 ?
				    AF_DONE : AF_DATA;
			}
			if (a->state == AF_DATA)
				s -= s % au_ssize(&a->au);
			break;
		case AF_DATA:
			if (a->au.au_size == UINT32_MAX)
				r = nread(a->fd, b, s);
			else
				r = nread(a->fd, b, MIN(s,
				    a->au.au_size - a->index));
			if (r <= 0)
				return (r);
			if ((a->index += r) >= a->au.au_size &&
			    a->au.au_size != UINT32_MAX)
				a->state = AF_DONE;
			return (r);
		case AF_DONE:
			/* FALLTHROUGH */
		case AF_EOF:
			return (0);
		}
	/* NOTREACHED */
}

struct timespec
af_seek(struct afile *a, struct timespec ts, int whence)
{
	const struct timespec tserr = { -1, -1 };
	int64_t ssize;
	int64_t s;
	off_t t;

	if (a == NULL)
		ERR(EINVAL, tserr);
	if (a->data < 0)
		ERR(EOPNOTSUPP, tserr);
	ssize = au_ssize(&a->au);
	s = ts.tv_sec * (int64_t)a->au.au_rate + ts.tv_nsec *
	    (int64_t)a->au.au_rate / INT64_C(1000000000);
	s *= ssize;
	switch (whence) {
	case SEEK_SET:
		break;
	case SEEK_END:
		s = (int64_t)a->au.au_size - s;
		break;
	case SEEK_CUR:
		s += a->index;
		break;
	default:
		ERR(EINVAL, tserr);
	}
	if (s < 0)
		s = 0;
	else if (s > a->au.au_size)
		s = a->au.au_size;
	if (s != a->index) {
		if ((t = lseek(a->fd, (off_t)a->data + s, SEEK_SET))
		    < 0)
			return (tserr);
		s = t - a->data;
		a->index = s;
	}
	s /= ssize;
	ts.tv_sec = s / a->au.au_rate;
	ts.tv_nsec = (s % a->au.au_rate) * INT64_C(1000000000) /
	    a->au.au_rate;
	return (ts);
}

struct timespec
af_length(struct afile *a)
{
	const struct timespec tserr = { -1, -1 };
	struct timespec ts;
	uint32_t s;

	if (a == NULL)
		ERR(EINVAL, tserr);
	if (a->state != AF_DATA || a->au.au_size == UINT32_MAX)
		ERR(EOPNOTSUPP, tserr);
	s = a->au.au_size / au_ssize(&a->au);
	ts.tv_sec = s / a->au.au_rate;
	ts.tv_nsec = (uint64_t)(s % a->au.au_rate) *
	    UINT64_C(1000000000) / (uint64_t)a->au.au_rate;
	return (ts);
}
